//
// Twitter.cs
//
// Copyright (C) 2008 Lukas Lipka <lukaslipka@gmail.com>
//

// Hey you!
//
// The documentation spec is available at:
//
//   http://groups.google.com/group/twitter-development-talk/web/api-documentation

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;

using Dashboard.Util;

namespace Twitter {

	public class TwitterClient {
		
		private const string TWITTER_WS = "http://twitter.com/";

		private ICredentials credentials = null;
		
		public TwitterClient (string username, string password)
		{
			this.credentials = new NetworkCredential (username, password);
		}

		public static TwitterCollection<TwitterStatus> PublicTimeline ()
		{
			XmlDocument document = WebpageFu.GetXml (BuildUri ("statuses/public_timeline.xml"));
			return new TwitterCollection<TwitterStatus> (document.SelectSingleNode ("statuses"), "status");
		}

		public static TwitterCollection<TwitterStatus> FriendsTimeline (string username)
		{
			XmlDocument document = WebpageFu.GetXml (BuildUri ("statuses/friends_timeline/{0}.xml", username));
			return new TwitterCollection<TwitterStatus> (document.SelectSingleNode ("statuses"), "status");
		}

		public TwitterCollection<TwitterStatus> FriendsTimeline ()
		{
			string uri = "http://twitter.com/statuses/friends_timeline.xml";

			HttpWebRequest request = WebRequest.Create (uri) as HttpWebRequest;
			request.Credentials = credentials;
			
			XmlDocument document = WebpageFu.GetXml (request);
			return new TwitterCollection<TwitterStatus> (document.SelectSingleNode ("statuses"), "status");
		}

		public static TwitterCollection<TwitterStatus> UserTimeline (string username)
		{
			XmlDocument document = WebpageFu.GetXml (BuildUri ("statuses/user_timeline/{0}.xml", username));
			return new TwitterCollection<TwitterStatus> (document.SelectSingleNode ("statuses"), "status");
		}

		public TwitterCollection<TwitterStatus> UserTimeline ()
		{
			string uri = "http://twitter.com/statuses/user_timeline.xml";

			HttpWebRequest request = WebRequest.Create (uri) as HttpWebRequest;
			request.Credentials = credentials;

			XmlDocument document = WebpageFu.GetXml (request);
			return new TwitterCollection<TwitterStatus> (document.SelectSingleNode ("statuses"), "status");
		}
		
		public static TwitterStatus Show (string id)
		{
			XmlDocument document = WebpageFu.GetXml (BuildUri ("statuses/show/{0}.xml", id));
			return new TwitterStatus (document.SelectSingleNode ("status"));
		}

		public void Update (string status)
		{
			string uri = "http://twitter.com/statuses/update.xml";
			
			if (status.Length > 160)
				throw new NotSupportedException ("Status length exceeds 160 characters");

			string post_data = "status=" + System.Web.HttpUtility.UrlEncode (status);

			UTF8Encoding encoding = new UTF8Encoding ();
			byte[] data = encoding.GetBytes (post_data);

			HttpWebRequest request = WebRequest.Create (uri) as HttpWebRequest;
			request.Method = "POST";
			request.Credentials = credentials;
			request.ContentLength = data.Length;

			Stream stream = request.GetRequestStream ();
			stream.Write (data, 0, data.Length);
			stream.Close ();

			request.GetResponse ();
		}

		public static bool VerifyCredentials (string username, string password)
		{
			string uri = "http://twitter.com/account/verify_credentials.xml";
			
			HttpWebRequest request = WebRequest.Create (uri) as HttpWebRequest;
			request.Credentials = new NetworkCredential (username, password);

			HttpWebResponse response = null;

			try {
				response = request.GetResponse () as HttpWebResponse;

				if (response.StatusCode == HttpStatusCode.OK)
					return true;
			} catch (WebException e) {
				if (e.Status == WebExceptionStatus.ProtocolError)
					return false;

				throw e;
			} finally {
				if (response != null)
					response.Close ();
			}

			return false;
		}

		private static string BuildUri (string fragment, params string[] args)
		{			
			return TWITTER_WS + String.Format (fragment, args);
		}
	}

	public abstract class TwitterBase {

		private XmlNode root_node = null;

		public TwitterBase (XmlNode root_node, string root_node_name)
		{
			this.root_node = root_node.SelectSingleNode (root_node_name);
		}

		public TwitterBase (XmlNode root_node)
		{
			this.root_node = root_node;
		}

		public T Get<T> (string node)
		{
			if (root_node [node] == null || String.IsNullOrEmpty (root_node [node].InnerText))
				return default (T);

			return (T) Convert.ChangeType (root_node [node].InnerText.Trim (), typeof (T));
		}

		public T GetChild<T> (string node, string child)
		{
			XmlNode parent_node = root_node [node];

			if (parent_node == null || parent_node [child] == null)
				return default (T);
			
			if (String.IsNullOrEmpty (parent_node [child].InnerText))
				return default (T);

			return (T) Convert.ChangeType (parent_node [child].InnerText.Trim (), typeof (T));
		}

		public T GetAttribute<T> (string node, string attribute)
		{
			if (root_node [node] == null || String.IsNullOrEmpty (root_node [node].GetAttribute (attribute)))
				return default (T);

			return (T) Convert.ChangeType (root_node [node].GetAttribute (attribute).Trim (), typeof (T));
		}

		public T GetAttribute<T> (string attribute)
		{
			if (root_node == null || String.IsNullOrEmpty (root_node.Attributes [attribute].InnerText))
				return default (T);

			return (T) Convert.ChangeType (root_node.Attributes [attribute].InnerText.Trim (), typeof (T));
		}

		public T GetType<T> (string node)
		{
			if (root_node == null)
				return default (T);

			return (T) Activator.CreateInstance (typeof (T), root_node [node]);
		}
		
		public TwitterCollection<T> GetCollection<T> (string node, string node_type) where T : TwitterBase
		{
			return new TwitterCollection<T> (root_node [node], node_type);
		}
	}

	public class TwitterCollection<T> : IEnumerable<T> where T : TwitterBase
	{
		private List<T> collection = new List<T> ();

		public TwitterCollection (XmlNode root_node, string root_node_type)
		{
			foreach (XmlNode node in root_node.SelectNodes (root_node_type)) {
				T t = (T) Activator.CreateInstance (typeof (T), node);
				collection.Add (t);
			}
		}

		public IEnumerator<T> GetEnumerator ()
		{
			return collection.GetEnumerator ();
		}

		IEnumerator IEnumerable.GetEnumerator ()
		{
			return collection.GetEnumerator ();
		}
	}

	public class TwitterUser : TwitterBase {	
		public TwitterUser (XmlNode node) : base (node) { }

		public string Id { get { return Get<string> ("id"); } }
		public string Name { get { return Get<string> ("name"); } }
		public string ScreenName { get { return Get<string> ("screen_name"); } }
		public string Location { get { return Get<string> ("location"); } }
		public string Description { get { return Get<string> ("description"); } }
		public string ProfileImageUrl { get { return Get<string> ("profile_image_url"); } }
		public string Url { get { return Get<string> ("url"); } }
		public bool Protected { get { return Get<bool> ("protected"); } }
	}

	public class TwitterStatus : TwitterBase {
		public TwitterStatus (XmlNode node) : base (node) { }

		public string CreatedAt { get { return Get<string> ("created_at"); } }
		public string Id { get { return Get<string> ("id"); } }
		public string Text { get { return Get<string> ("text"); } }
		public string Source { get { return Get<string> ("source"); } }
		public string Truncated { get { return Get<string> ("truncated"); } }
		public TwitterUser User { get { return GetType<TwitterUser> ("user"); } }
	}
}